import java.util.Random;
import java.util.concurrent.Semaphore;

public class Office {
	public static void main(String[] args) {
		new Office();
	}
	
	private static int TOTAL_CUSTOMERS = 3;
	private static int TOTAL_PROGRAMMERS = 7;
	private static int REQUIRED_PROGRAMMERS = 2;
	
	private Thread teamLeader;
	private Thread[] customers;
	private Thread[] programmers;

	public int queueCustomers = 0;
	public int queueProgrammers = 0;

	public Semaphore mutexCustomer;
	public Semaphore mutexProgrammer;
	public Semaphore waitingCustomers;
	public Semaphore waitingProgrammers;
	public Semaphore endOfMeeting;
	
	public boolean teamLeaderIsInOffice;
	
	public Office() {
		System.out.println("Program started, have some faith");

		mutexCustomer = new Semaphore(1);
		mutexProgrammer = new Semaphore(1);
		waitingCustomers = new Semaphore(0, true);
		waitingProgrammers = new Semaphore(0, true);
		endOfMeeting = new Semaphore(0, true);
		
		teamLeaderIsInOffice = true;
		teamLeader = new Teamleader("Teamleader");
		teamLeader.start();
		
		customers = new Thread[TOTAL_CUSTOMERS];
		for(int i = 0; i < TOTAL_CUSTOMERS; i++) {
			customers[i] = new Customer(i, "Customer #"+ i);
			customers[i].start();
		}
		
		programmers = new Thread[TOTAL_PROGRAMMERS];
		for(int i = 0; i < TOTAL_PROGRAMMERS; i++) {
			programmers[i] = new Programmer(i, "Programmer #"+ i);
			programmers[i].start();
		}
	}
	
	public void appearBusy() {
		Random random = new Random();
		try {
            Thread.sleep(random.nextInt(10000));
        } catch (InterruptedException e) {}
	}
	
	class Teamleader extends Thread {
		public Teamleader(String name) {
			super(name);
		}
		
		public void run() {
			while (true) {
				if(queueCustomers == TOTAL_CUSTOMERS) {
					teamLeaderIsInOffice = false;
					
					// Haal de wachtende klanten uit de wachtkamer.
					waitingCustomers.release(TOTAL_CUSTOMERS);
					queueCustomers = 0;
					
					// Eventuele wachtende programmeurs vertellen dat ze weer moeten gaan werken.
					waitingProgrammers.release(queueProgrammers);
					queueProgrammers = 0;
					
					// Tijd simuleren
					appearBusy();
					// Meeting is voorbij, alle klanten laten weten.
					endOfMeeting.release(TOTAL_CUSTOMERS);
					teamLeaderIsInOffice = true;
				} else if(queueProgrammers == REQUIRED_PROGRAMMERS) {
					// Haal 2 programmeurs uit de wachtkamer.
					waitingProgrammers.release(REQUIRED_PROGRAMMERS);
					queueProgrammers -= REQUIRED_PROGRAMMERS;

					// Tijd simuleren
					appearBusy();
					// Meeting is voorbij, alle programmeurs laten weten.
					endOfMeeting.release(REQUIRED_PROGRAMMERS);
				}
			}
		}
	}
	
	class Customer extends Thread {
		private int myId;
		
		public Customer(int id, String name) {
			super(name);
			myId = id;
		}
		
		public void run() {
			while (true) {
				appearBusy();

				// Verhoog de teller van het aantal wachtende klanten met 1
				// En scherm dat af met een mutex
				mutexCustomer.acquireUninterruptibly();
				queueCustomers++;
				mutexCustomer.release();

				System.out.println("Customer #"+ myId +". > Wacht voor het begin van de vergadering");
				waitingCustomers.acquireUninterruptibly();
				System.out.println("Customer #"+ myId +". > In de vergadering");
				endOfMeeting.acquireUninterruptibly();
				System.out.println("Customer #"+ myId +". > Klaar met vergadering");
			}
		}
	}
	
	class Programmer extends Thread {
		private int myId;
		
		public Programmer(int id, String name) {
			super(name);
			myId = id;
		}
		
		public void run() {
			while (true) {
				appearBusy();

				if(teamLeaderIsInOffice) {
					// Verhoog de teller van het aantal wachtende programmeurs met 1
					// En scherm dat af met een mutex
					mutexProgrammer.acquireUninterruptibly();
					queueProgrammers++;
					mutexProgrammer.release();

					System.out.println("\tProgrammer #"+ myId +". > Wacht voor het begin van de vergadering");
					waitingProgrammers.acquireUninterruptibly();
					if(teamLeaderIsInOffice) {
						System.out.println("\tProgrammer #"+ myId +". > In de vergadering");
						
						// Laat weten dat de vergadering voorbij is.
						endOfMeeting.acquireUninterruptibly();
						System.out.println("\tProgrammer #"+ myId +". > Klaar met vergadering");
					} else {
						System.out.println("\tProgrammer #"+ myId +". > Vergadering met klanten kwam tussendoor, terug gaan aan het werk");
					}
				} else {
					System.out.println("\tProgrammer #"+ myId +". > Terug gaan aan het werk omdat de teamleader niet aanwezig is");
				}
			}
		}
	}
}
